package com.sunnada.nginx.service;

import com.github.odiszapc.nginxparser.NgxBlock;
import com.github.odiszapc.nginxparser.NgxConfig;
import com.github.odiszapc.nginxparser.NgxEntry;
import com.sunnada.nginx.bean.nginx.http.HttpServer;
import com.sunnada.nginx.bean.nginx.http.Location;
import com.sunnada.nginx.common.NginxUtils;
import com.sunnada.nginx.exception.CustonException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service("ruleService")
public class RuleServiceImpl implements RuleService {

    @Autowired
    NginxUtils nginxUtils;

    @Autowired
    StatusService statusService;

    @Override
    public List<Location> list(String nameAndPort) throws CustonException {
        List<Location> result = new ArrayList<>();
        List<HttpServer> temp = new ArrayList<>();
        boolean nameAndPortIsBlank = StringUtils.isBlank(nameAndPort);
        NgxConfig conf = nginxUtils.read();
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "server");
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            HttpServer server = new HttpServer(block);
            if (nameAndPortIsBlank) temp.add(server);
            else if (nameAndPort.equals(server.getNameAndPort())) temp.add(server);
        }
        for (HttpServer httpServer : temp) {
            result.addAll(httpServer.getLocations());
        }
        return result;
    }

    @Override
    public Object get(String name, String port, String rule) throws CustonException {
        HttpServer server = null;
        NgxConfig conf = nginxUtils.read();
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "server");
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            HttpServer temp = new HttpServer(block);
            if (temp.getNameAndPort().equals(name + ":" + port)) {
                server = temp;
                break;
            }
        }
        if (server != null) {
            List<Location> list = server.getLocations();
            for (Location location : list) {
                if (location.getPath().equals(rule)) return location;
            }
        }
        return null;
    }

    @Override
    public boolean post(String name, String port, Location location) throws CustonException {
        location.vali();
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断location是否存在
        // 存在则抛出异常，不存在则执行添加操作
        if (LocationIsExistInConf(conf, name, port, location.getPath())) throw new CustonException("该location已存在，无法新增");
        addLocation(conf, name, port, location);
        checkConf(conf, backConf);
        return true;
    }

    private void addLocation(NgxConfig conf, String name, String port, Location location) {
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "server");
        for (NgxEntry entry : findAll) {
            NgxBlock block = (NgxBlock) entry;
            HttpServer temp = new HttpServer(block);
            if (temp.getNameAndPort().equals(name + ":" + port)) {
                block.addEntry(location.toNgxBlock());
            }
        }
    }

    @Override
    public boolean put(String name, String port, Location location) throws CustonException {
        location.vali();
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断location是否存在
        // 不存在则抛出异常，存在则执行添加操作
        if (!LocationIsExistInConf(conf, name, port, location.getPath()))
            throw new CustonException("该location不存在，无法修改");
        // 修改配置中的同名location
        updateLocation(conf, name, port, location);
        checkConf(conf, backConf);
        return true;
    }

    private void updateLocation(NgxConfig conf, String name, String port, Location location) {
        deleteLocation(conf, name, port, location.getPath());
        addLocation(conf,name,port,location);
    }

    @Override
    public boolean delete(String name, String port, String rule) throws CustonException {
        // 读取配置
        NgxConfig conf = nginxUtils.read();
        // 备份配置
        String backConf = NginxUtils.toString(conf);
        // 判断httpServer是否存在
        // 不存在则抛出异常，存在则执行添加操作
        if (!LocationIsExistInConf(conf, name, port, rule)) throw new CustonException("该location不存在，无法删除");
        // 删除配置中的location
        deleteLocation(conf, name, port, rule);
        checkConf(conf, backConf);
        return false;
    }

    /**
     * @see RuleServiceImpl#deleteLocation(NgxConfig, String, String, String)
     */
    private void deleteLocation(NgxConfig conf, Location location) {
        deleteLocation(conf, location.getServerName(), location.getServerPort(), location.getPath());
    }

    private void deleteLocation(NgxConfig conf, String name, String port, String path) {
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "server");
        for (NgxEntry entry : findAll) {
            NgxBlock block = (NgxBlock) entry;
            HttpServer temp = new HttpServer(block);
            if (temp.getNameAndPort().equals(name + ":" + port)) {
                List<NgxEntry> locations = block.findAll(NgxConfig.BLOCK, "location");
                for (NgxEntry location : locations) {
                    NgxBlock locationBlock = (NgxBlock) location;
                    Location temp2 = new Location(name, port, locationBlock);
                    if (temp2.getPath().equals(path)) {
                        block.remove(location);
                        break;
                    }
                }
            }
        }
    }

    private boolean LocationIsExistInConf(NgxConfig conf, Location location) {
        return LocationIsExistInConf(conf, location.getServerName(), location.getServerPort(), location.getPath());
    }

    private boolean LocationIsExistInConf(NgxConfig conf, String name, String port, String path) {
        List<NgxEntry> findAll = conf.findAll(NgxConfig.BLOCK, "http", "server");
        HttpServer server = null;
        for (NgxEntry ngxEntry : findAll) {
            NgxBlock block = (NgxBlock) ngxEntry;
            HttpServer temp = new HttpServer(block);
            if (temp.getNameAndPort().equals(name + ":" + port)) {
                server = temp;
                break;
            }
        }
        if (server == null) return false;
        for (Location location : server.getLocations()) {
            if (!location.getPath().equals(path)) continue;
            return true;
        }
        return false;
    }

    /**
     * 使用新的配置重启ngixn，如果重启失败，则回滚配置
     *
     * @param conf 新配置
     * @param back 配置备份
     */
    private void checkConf(NgxConfig conf, String back) throws CustonException {
        try {
            nginxUtils.save(conf);
            //重启Nginx
            statusService.checkConf();
        } catch (Exception e) {
            //恢复到上一次配置
            nginxUtils.save(back);
            throw new CustonException("已恢复到上一次配置");
        }
    }
}
